package com.codeoss.framework.view.datasource;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.codeoss.framework.context.YunContext;
import com.codeoss.framework.data.metadata.Column;
import com.codeoss.framework.data.metadata.Expression;
import com.codeoss.framework.data.metadata.Order;
import com.codeoss.framework.data.metadata.Query;
import com.codeoss.framework.data.metadata.Table;
import com.codeoss.framework.data.metadata.TableData;
import com.codeoss.framework.db.util.ColumnChangeLoggingUtil;
import com.codeoss.framework.db.util.ModelAccessLoggingUtil;
import com.codeoss.framework.model.BaseModel;
import com.codeoss.framework.service.impl.BaseService;
import com.codeoss.framework.util.PermissionUtil;
import com.codeoss.framework.util.StringUtils;
import com.codeoss.framework.util.Utils;
import com.codeoss.framework.view.datasource.store.IDataSourceStore;
import com.codeoss.framework.view.util.TableCRUDRuleUtil;
import com.codeoss.framework.view.util.ValidatorUtil;

@Service
public class MybatisDataSource implements IDataSource {
	@Autowired
	private ValidatorUtil validatorUtil;
	@Autowired
	private PermissionUtil permissionUtil;
	@Autowired
	private TableCRUDRuleUtil tableCURDRuleUtil;
	@Autowired
	private Utils utils;
	 @Value("${framework.db-type}")
		private String dbType;
	 @Autowired
	 private ColumnChangeLoggingUtil columnChangeLoggingUtil;
	 @Autowired
	 private ModelAccessLoggingUtil modelAccessLoggingUtil;
	 @Autowired
	 private IDataSourceStore dataSourceStore;
	protected QueryWrapper<Object> getQueryWrapperFromQuery(Table table,Query query,YunContext yunContext) {
		QueryWrapper<Object> queryWrapper = new QueryWrapper<Object>();
		if(query==null) {
			return queryWrapper;
		}
		if (query.getSelectColumnList() != null && query.getSelectColumnList().size() > 0) {
			queryWrapper.select(query.getSelectColumnList().toArray(new String[query.getSelectColumnList().size()]));
		}
		if (query != null) {
			boolean hasExpression = false;
			List<Expression> expressionList = query.getExpressionList();
			List<Expression> sqlExpressionList = new ArrayList<Expression>();
			for (int i = 0; i < expressionList.size(); i++) {
				Expression expression = expressionList.get(i);
				String operation = expression.getOperation();
				String name = expression.getName();
				if ("like".equals(operation)) {
					queryWrapper.like(name, expression.getValue1());
					hasExpression = true;
				} else if ("likeLeft".equals(operation)) {
					queryWrapper.likeLeft(name, expression.getValue1());
					hasExpression = true;
				} else if ("likeRight".equals(operation)) {
					queryWrapper.likeRight(name, expression.getValue1());
					hasExpression = true;
				} else if ("between".equals(operation)) {
					queryWrapper.between(name, expression.getValue1(), expression.getValue2());
					hasExpression = true;
				} else if ("=".equals(operation)) {
					Object value1 = expression.getValue1();
					if (value1 != null && value1 instanceof Boolean) {
						queryWrapper.eq(name, value1.toString());
					} else {
						queryWrapper.eq(name, value1);
					}
					hasExpression = true;
				} else if ("<>".equals(operation)) {
					queryWrapper.ne(name, expression.getValue1());
					hasExpression = true;
				} else if (">".equals(operation)) {
					queryWrapper.gt(name, expression.getValue1());
					hasExpression = true;
				} else if ("<".equals(operation)) {
					queryWrapper.lt(name, expression);
					hasExpression = true;
				} else if (">=".equals(operation)) {
					queryWrapper.ge(name, expression.getValue1());
					hasExpression = true;
				} else if ("<=".equals(operation)) {
					queryWrapper.le(name, expression.getValue1());
					hasExpression = true;
				} else if ("in".equals(operation)) {
					queryWrapper.in(name, (Collection) expression.getValue1());
					hasExpression = true;
				} else if ("notin".equals(operation)) {
					queryWrapper.notIn(name, expression.getValue1());
					hasExpression = true;
				} else if ("isnull".equals(operation)) {
					queryWrapper.isNull(name);
					hasExpression = true;
				} else if ("isnotnull".equals(operation)) {
					queryWrapper.isNotNull(name);
					hasExpression = true;
				} else {
					String sql = expression.getSQL();
					if (!StringUtils.isEmpty(sql)) {
						sqlExpressionList.add(expression);
					}
				}
			}
			if (sqlExpressionList.size() > 0) {
				StringBuilder sql = new StringBuilder();
				if (hasExpression) {
					sql.append(" and ");
				} else {
					sql.append(" where ");
				}
				for (int i = 0; i < sqlExpressionList.size(); i++) {
					if (i > 0) {
						sql.append(" and ");
					}
					sql.append("(");
					sql.append(sqlExpressionList.get(i).getSQL());
					sql.append(")");
				}
				String extraSql=tableCURDRuleUtil.doSelectRule(yunContext, table,"select");
				if(!StringUtils.isEmpty(extraSql)) {
					sql.append(" and (").append(extraSql).append(") ");
				}
				List<Order> orderList = query.getOrderList();
				if(orderList.size()>0) {
					sql.append(" order by ");
				}
				for (int i = 0; i < orderList.size(); i++) {
					if(i>0) {
						sql.append(",");
					}
					Order order = orderList.get(i);
					String orderType = order.getOrder();
					if(orderType!=null) {
						if ("asc".equals(orderType)) {
							sql.append(order.getName()).append(" asc ");
						} else {
							sql.append(order.getName()).append(" desc ");
						}
					}else {
						String orderSql=order.getOrderSQL();
						if(!StringUtils.isEmpty(orderSql)) {
							String orderSqlArray[]=orderSql.split(",");
							for(int j=0;j<orderSqlArray.length;j++) {
								if(j>0) {
									sql.append(",");
								}
								String orderSqlString=orderSqlArray[j];
								String orderArray[]=orderSqlString.split(" ");
								if(orderArray[1].equals("asc")) {
									sql.append(orderArray[0]).append(" asc ");
								}else {
									sql.append(orderArray[0]).append(" desc ");
								}
							}
						}
					}
				}
				queryWrapper.last(sql.toString());

			} else {
				StringBuilder sql = new StringBuilder();
				String extraSql=tableCURDRuleUtil.doSelectRule(yunContext, table,"select");
				if(!StringUtils.isEmpty(extraSql)) {
					if(hasExpression) {
						sql.append(" and ");
					}else {
						sql.append(" where ");
					}
					sql.append("  (").append(extraSql).append(") ");
					List<Order> orderList = query.getOrderList();
					if(orderList.size()>0) {
						sql.append(" order by ");
					}
					for (int i = 0; i < orderList.size(); i++) {
						if(i>0) {
							sql.append(",");
						}
						Order order = orderList.get(i);
						String orderType = order.getOrder();
						if(orderType!=null) {
							if ("asc".equals(orderType)) {
								sql.append(order.getName()).append(" asc ");
							} else {
								sql.append(order.getName()).append(" desc ");
							}
						}else {
							String orderSql=order.getOrderSQL();
							if(!StringUtils.isEmpty(orderSql)) {
								String orderSqlArray[]=orderSql.split(",");
								for(int j=0;j<orderSqlArray.length;j++) {
									if(j>0) {
										sql.append(",");
									}
									String orderSqlString=orderSqlArray[j];
									String orderArray[]=orderSqlString.split(" ");
									if(orderArray[1].equals("asc")) {
										sql.append(orderArray[0]).append(" asc ");
									}else {
										sql.append(orderArray[0]).append(" desc ");
									}
								}
							}
						}
					}
					queryWrapper.last(sql.toString());
				}else {
					List<Order> orderList = query.getOrderList();
					for (int i = 0; i < orderList.size(); i++) {
						Order order = orderList.get(i);
						String orderType = order.getOrder();
						if(orderType!=null) {
							if ("asc".equals(orderType)) {
								queryWrapper.orderByAsc(order.getName());
							} else {
								queryWrapper.orderByDesc(order.getName());
							}
						}else {
							String orderSql=order.getOrderSQL();
							if(!StringUtils.isEmpty(orderSql)) {
								String orderSqlArray[]=orderSql.split(",");
								for(int j=0;j<orderSqlArray.length;j++) {
									String orderSqlString=orderSqlArray[j];
									String orderArray[]=orderSqlString.split(" ");
									if(orderArray[1].equals("asc")) {
										queryWrapper.orderByAsc(orderArray[0]);
									}else {
										queryWrapper.orderByDesc(orderArray[0]);
									}
								}
							}
						}
					}
				}	
			}
		}
		return queryWrapper;
	}

	@Override
	public TableData select(YunContext yunContext, Table table, Query query) {
		// TODO Auto-generated method stub
		IDataSource dataSource=dataSourceStore.getDataSource("db");
		return dataSource.select(yunContext, table, query);
	}

	@Override
	public void delete(YunContext yunContext, Table table, Map<String, Object> dataMap) {
		// TODO Auto-generated method stub
		Object keyValue=dataMap.get(table.getPrimaryKeyColumn().getColumnName());
		if(keyValue==null) {
			return;
		}
		permissionUtil.hasPermission(table,"delete");
		tableCURDRuleUtil.doRule(yunContext, table, dataMap, TableCRUDRuleUtil.TABLE_CURD_BEFORE_DELETE);
		BaseService baseService = utils.getBaseService(table);
		QueryWrapper<Object> queryWrapper = new QueryWrapper<Object>();
		queryWrapper.eq(table.getPrimaryKeyColumn().getColumnName(),keyValue);
		Object model = baseService.getOne(queryWrapper);
		
		String extraSql=tableCURDRuleUtil.doSelectRule(yunContext, table,"delete");
	    if(!StringUtils.isEmpty(extraSql)) {
	    	queryWrapper.last(extraSql);
	    	baseService.delete(model, queryWrapper);
	    }else {
	    	baseService.delete(model);
	    }
	    tableCURDRuleUtil.doRule(yunContext, table, dataMap, TableCRUDRuleUtil.TABLE_CURD_AFTER_DELETE);
	    modelAccessLoggingUtil.logDataDelete(yunContext,table);
	}

	@Override
	public void insert(YunContext yunContext, Table table, Map<String, Object> dataMap) {
		// TODO Auto-generated method stub
		validatorUtil.validate(yunContext, table, dataMap);
		
		permissionUtil.hasPermission(table,"insert");
		
		tableCURDRuleUtil.doRule(yunContext, table, dataMap, TableCRUDRuleUtil.TABLE_CURD_BEFORE_INSERT);
		
		BaseService baseService = utils.getBaseService(table);
		Object modelObject = utils.newModelObject(table);
		List<Column> columnList = table.getAllColumnList();
		for (int i = 0; i < columnList.size(); i++) {
			Column column = columnList.get(i);
			if (column.isPrimaryKey()) {
				continue;
			}
			if(!column.isStoreColumn()) {
				continue;
			}
			String columnName = column.getColumnName();
			if(dataMap.containsKey(columnName)) {
				Object value = dataMap.get(columnName);
				try {
					BeanUtils.setProperty(modelObject, column.getProperty(), value);
				} catch (IllegalAccessException | InvocationTargetException e) {
					// TODO Auto-generated catch block
					throw new RuntimeException(e);
				}
			}
		}
		baseService.insert(modelObject);
		Column primaryKeyColumn = table.getPrimaryKeyColumn();
		if (primaryKeyColumn != null) {
			BaseModel baseModel = (BaseModel) modelObject;
			dataMap.put(primaryKeyColumn.getColumnName(), baseModel.getId());
		}
		tableCURDRuleUtil.doRule(yunContext, table, dataMap, TableCRUDRuleUtil.TABLE_CURD_AFTER_INSERT);
		columnChangeLoggingUtil.logDataInsert(yunContext, table, dataMap);
		 modelAccessLoggingUtil.logDataInsert(yunContext, table,(Long)dataMap.get(primaryKeyColumn.getColumnName()));
	}

	@Override
	public void update(YunContext yunContext, Table table, Map<String, Object> dataMap) {
		// TODO Auto-generated method stub
		validatorUtil.validate(yunContext, table, dataMap);
		
		permissionUtil.hasPermission(table,"update");
		
		tableCURDRuleUtil.doRule(yunContext, table, dataMap, TableCRUDRuleUtil.TABLE_CURD_BEFORE_UPDATE);
		
		BaseService baseService = utils.getBaseService(table);
		Map<String,Object> oldValueMap=null;
		if(table.getLoggingColumnList().size()>0) {
			Column primaryKeyColumn = table.getPrimaryKeyColumn();
			QueryWrapper<Object> queryWrapper = new QueryWrapper<Object>();
			queryWrapper.eq(primaryKeyColumn.getColumnName(), dataMap.get(primaryKeyColumn.getColumnName()));
			oldValueMap=baseService.getMap(queryWrapper);
		}
		
		Column primaryKeyColumn = table.getPrimaryKeyColumn();
		QueryWrapper<Object> queryWrapper = new QueryWrapper<Object>();
		queryWrapper.eq(primaryKeyColumn.getColumnName(), dataMap.get(primaryKeyColumn.getColumnName()));
		Object model = baseService.getOne(queryWrapper);
		Iterator<Map.Entry<String, Object>> it = dataMap.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry<String, Object> entry = it.next();
			String key = entry.getKey();
			Column column=table.getColumnByColumnName(key);
			if(!column.isStoreColumn()) {
				continue;
			}
			Object value = entry.getValue();
			String propertyName = utils.getPropertyName(key);
			try {
				BeanUtils.setProperty(model, propertyName, value);
			} catch (IllegalAccessException | InvocationTargetException e) {
				// TODO Auto-generated catch block
				throw new RuntimeException(e);
			}
		}
		String extraSql=tableCURDRuleUtil.doSelectRule(yunContext, table,"update");
	    if(!StringUtils.isEmpty(extraSql)) {
	    	queryWrapper.eq(primaryKeyColumn.getColumnName(), dataMap.get(primaryKeyColumn.getColumnName()));
	    	queryWrapper.last(extraSql);
	    	baseService.update(model, queryWrapper);
	    }else {
	    	baseService.update(model);
	    }
	    
	    Map<String,Object> newValueMap=null;
		if(table.getLoggingColumnList().size()>0) {
			QueryWrapper<Object> newDataQueryWrapper = new QueryWrapper<Object>();
			newDataQueryWrapper.eq(primaryKeyColumn.getColumnName(), dataMap.get(primaryKeyColumn.getColumnName()));
			newValueMap=baseService.getMap(newDataQueryWrapper);
		}
	    tableCURDRuleUtil.doRule(yunContext, table, dataMap, TableCRUDRuleUtil.TABLE_CURD_AFTER_UPDATE);
	    if(oldValueMap!=null&&newValueMap!=null) {
			columnChangeLoggingUtil.logDataUpdate(yunContext, table, oldValueMap, newValueMap);
		}
	    modelAccessLoggingUtil.logDataUpdate(yunContext, table,(Long)dataMap.get(primaryKeyColumn.getColumnName()));
	}
}
